package com.example.redisdemo.accessLimit.accessLimitInterceptor;

import com.example.redisdemo.accessLimit.annotation.AccessLimit;
import com.example.redisdemo.accessLimit.exception.ServiceException;
import com.example.redisdemo.accessLimit.util.Constant;
import com.example.redisdemo.accessLimit.util.IpUtil;
import com.example.redisdemo.apiIdempotent.annotation.ApiIdempotent;
import com.example.redisdemo.apiIdempotent.service.TokenService;
import com.example.redisdemo.util.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @Description 接口防刷限流拦截器
 * @ClassName AccessLimitInterceptor
 * @Author jiang_11445266119198
 * @Date 2022/7/18 15:55
 */
public class AccessLimitInterceptor implements HandlerInterceptor {

    @Autowired
    private TokenService tokenService;
    @Resource
    private StringRedisTemplate stringTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("== 1 ==");
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();

        AccessLimit annotation = method.getAnnotation(AccessLimit.class);
        if (annotation != null) {
            check(annotation, request);
        }
        ApiIdempotent methodAnnotation = method.getAnnotation(ApiIdempotent.class);
        if (methodAnnotation != null) {
            apiIdempotentCheck(request);// 幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示
        }

        return true;
    }

    private void  apiIdempotentCheck(HttpServletRequest request) {
        tokenService.checkToken(request);
    }


    private void check(AccessLimit annotation, HttpServletRequest request) {
        int maxCount = annotation.maxCount();
        int seconds = annotation.seconds();

        StringBuilder sb = new StringBuilder();
        sb.append(Constant.Redis.ACCESS_LIMIT_PREFIX).append(IpUtil.getIpAddress(request)).append(request.getRequestURI());
        String key = sb.toString();

        ValueOperations<String, String> valueTemplate = stringTemplate.opsForValue();

        /**
         * 从redis中获取key对应的过期时间;
         * 如果该值有过期时间，就返回相应的过期时间;
         * 如果该值没有设置过期时间，就返回-1;
         * 如果没有该值，就返回-2;
         */
        Long expire = valueTemplate.getOperations().getExpire(key);
        //System.out.println("expire = " + expire);

        if (expire == -2 || expire == 0) {     //  不存在该值
            valueTemplate.set(key, String.valueOf(1), seconds, TimeUnit.SECONDS);
        } else {
            if (expire == -1) { // 没有设置过期时间
                valueTemplate.set(key, String.valueOf(1), seconds, TimeUnit.SECONDS);
            } else {            // 有过期时间 ，则进行次数判断
                int count = Integer.valueOf(valueTemplate.get(key));
                if (count < maxCount) {
                    valueTemplate.set(key, String.valueOf(++count), expire.intValue(), TimeUnit.SECONDS);
                } else {
                    throw new ServiceException(HttpStatus.ACCESS_LIMIT.getMsg());
                }
            }
        }
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    }


}
